package com.jellyfishumbrella.GL;

/**
 * Copyright John E. Lloyd, 2004. All rights reserved. Permission to use,
 * copy, modify and redistribute is granted, provided that this copyright
 * notice is retained and the author is given credit whenever appropriate.
 *
 * This  software is distributed "as is", without any warranty, including 
 * any implied warranty of merchantability or fitness for a particular
 * use. The author assumes no responsibility for, and shall not be liable
 * for, any special, indirect, or consequential damages, or any damages
 * whatsoever, arising out of or in connection with the use of this
 * software.
 */

import java.util.Random;

import org.lwjgl.util.vector.Vector3f;

import com.jellyfishumbrella.util.EqualsUtil;
import com.jellyfishumbrella.util.HashCodeUtil;

/**
 * A three-element vector. This class is actually a reduced version of the
 * Vector3d class contained in the author's matlib package (which was partly
 * inspired by javax.vecmath). Only a mininal number of methods
 * which are relevant to convex hull generation are supplied here.
 * 
 * @author John E. Lloyd, Fall 2004
 */
public class Vector4d implements Cloneable// extends Vector3f
{
	protected int				fHashCode	= 0;
	/**
	 * Precision of a double.
	 */
	static private final double	DOUBLE_PREC	= 2.2204460492503131e-16;

	/**
	 * First element
	 */
	public double				x;

	/**
	 * Second element
	 */
	public double				y;

	/**
	 * Third element
	 */
	public double				z;

	/**
	 * Fourth element
	 */
	public double				w;

	/**
	 * Creates a 4-vector and initializes its elements to 0.
	 */
	public Vector4d() {}

	/**
	 * Creates a 3-vector by copying an existing one.
	 * 
	 * @param v
	 *            vector to be copied
	 */
	public Vector4d(Vector4d v) {
		set(v);
	}

	/**
	 * Creates a 3-vector with the supplied element values.
	 * 
	 * @param x
	 *            first element
	 * @param y
	 *            second element
	 * @param z
	 *            third element
	 */
	public Vector4d(double x, double y, double z, double w) {
		set(x, y, z, w);
	}

	/**
	 * Gets a single element of this vector.
	 * Elements 0, 1, and 2 correspond to x, y, and z.
	 * 
	 * @param i
	 *            element index
	 * @return element value throws ArrayIndexOutOfBoundsException
	 *         if i is not in the range 0 to 2.
	 */
	public double get(int i) {
		switch (i) {
			case 0: {
				return x;
			}
			case 1: {
				return y;
			}
			case 2: {
				return z;
			}
			case 3: {
				return w;
			}
			default: {
				throw new ArrayIndexOutOfBoundsException(i);
			}
		}
	}

	/**
	 * Sets a single element of this vector.
	 * Elements 0, 1, and 2 correspond to x, y, and z.
	 * 
	 * @param i
	 *            element index
	 * @param value
	 *            element value
	 * @return element value throws ArrayIndexOutOfBoundsException
	 *         if i is not in the range 0 to 2.
	 */
	public void set(int i, double value) {
		switch (i) {
			case 0: {
				x = value;
				break;
			}
			case 1: {
				y = value;
				break;
			}
			case 2: {
				z = value;
				break;
			}
			case 3: {
				w = value;
				break;
			}
			default: {
				throw new ArrayIndexOutOfBoundsException(i);
			}
		}
	}

	/**
	 * Sets the values of this vector to those of v1.
	 * 
	 * @param v1
	 *            vector whose values are copied
	 */
	public void set(Vector4d v1) {
		x = v1.x;
		y = v1.y;
		z = v1.z;
		w = v1.w;
	}

	/**
	 * Adds vector v1 to v2 and places the result in this vector.
	 * 
	 * @param v1
	 *            left-hand vector
	 * @param v2
	 *            right-hand vector
	 */
	public void add(Vector4d v1, Vector4d v2) {
		x = v1.x + v2.x;
		y = v1.y + v2.y;
		z = v1.z + v2.z;
		w = v1.w + v2.w;
	}

	/**
	 * Adds this vector to v1 and places the result in this vector.
	 * 
	 * @param v1
	 *            right-hand vector
	 */
	public void add(Vector4d v1) {
		x += v1.x;
		y += v1.y;
		z += v1.z;
		w += v1.w;
	}

	/**
	 * Subtracts vector v1 from v2 and places the result in this vector.
	 * 
	 * @param v1
	 *            left-hand vector
	 * @param v2
	 *            right-hand vector
	 */
	public void sub(Vector4d v1, Vector4d v2) {
		x = v1.x - v2.x;
		y = v1.y - v2.y;
		z = v1.z - v2.z;
		w = v1.w - v2.w;
	}

	/**
	 * Subtracts v1 from this vector and places the result in this vector.
	 * 
	 * @param v1
	 *            right-hand vector
	 */
	public void sub(Vector4d v1) {
		x -= v1.x;
		y -= v1.y;
		z -= v1.z;
		w -= v1.w;
	}

	/**
	 * Scales the elements of this vector by <code>s</code>.
	 * 
	 * @param s
	 *            scaling factor
	 */
	public void scale(double s) {
		x = s * x;
		y = s * y;
		z = s * z;
		w = s * w;
	}

	/**
	 * Scales the elements of vector v1 by <code>s</code> and places
	 * the results in this vector.
	 * 
	 * @param s
	 *            scaling factor
	 * @param v1
	 *            vector to be scaled
	 */
	public void scale(double s, Vector4d v1) {
		x = s * v1.x;
		y = s * v1.y;
		z = s * v1.z;
		w = s * v1.w;
	}

	/**
	 * Returns the 2 norm of this vector. This is the square root of the
	 * sum of the squares of the elements.
	 * 
	 * @return vector 2 norm
	 */
	public double norm() {
		return Math.sqrt(x * x + y * y + z * z + w * w);
	}

	/**
	 * Returns the square of the 2 norm of this vector. This
	 * is the sum of the squares of the elements.
	 * 
	 * @return square of the 2 norm
	 */
	public double normSquared() {
		return x * x + y * y + z * z + w * w;
	}

	/**
	 * Returns the Euclidean distance between this vector and vector v.
	 * 
	 * @return distance between this vector and v
	 */
	public double distance(Vector4d v) {

		return Math.sqrt(distanceSquared(v));
	}

	/**
	 * Returns the squared of the Euclidean distance between this vector
	 * and vector v.
	 * 
	 * @return squared distance between this vector and v
	 */
	public double distanceSquared(Vector4d v) {
		double dx = x - v.x;
		double dy = y - v.y;
		double dz = z - v.z;
		double dw = w - v.w;

		return (dx * dx + dy * dy + dz * dz + dw * dw);
	}

	/**
	 * Returns the dot product of this vector and v1.
	 * 
	 * @param v1
	 *            right-hand vector
	 * @return dot product
	 */
	public double dot(Vector4d v1) {
		return x * v1.x + y * v1.y + z * v1.z + w * v1.w;
	}

	/**
	 * Normalizes this vector in place.
	 */
	public void normalize() {
		double lenSqr = x * x + y * y + z * z + w * w;
		double err = lenSqr - 1;
		if (err > (2 * DOUBLE_PREC) || err < -(2 * DOUBLE_PREC)) {
			double len = Math.sqrt(lenSqr);
			x /= len;
			y /= len;
			z /= len;
			w /= len;
		}
	}

	/**
	 * Sets the elements of this vector to zero.
	 */
	public void setZero() {
		x = 0;
		y = 0;
		z = 0;
		w = 0;
	}

	/**
	 * Sets the elements of this vector to the prescribed values.
	 * 
	 * @param x
	 *            value for first element
	 * @param y
	 *            value for second element
	 * @param z
	 *            value for third element
	 */
	public void set(double x, double y, double z, double w) {
		this.x = x;
		this.y = y;
		this.z = z;
		this.w = w;
	}

	/**
	 * Sets the elements of this vector to uniformly distributed
	 * random values in a specified range, using a supplied
	 * random number generator.
	 * 
	 * @param lower
	 *            lower random value (inclusive)
	 * @param upper
	 *            upper random value (exclusive)
	 * @param generator
	 *            random number generator
	 */
	protected void setRandom(double lower, double upper, Random generator) {
		double range = upper - lower;

		x = generator.nextDouble() * range + lower;
		y = generator.nextDouble() * range + lower;
		z = generator.nextDouble() * range + lower;
		w = generator.nextDouble() * range + lower;
	}

	/**
	 * Returns a string representation of this vector, consisting
	 * of the x, y, and z coordinates.
	 * 
	 * @return string representation
	 */
	public String toString() {
		return x + " " + y + " " + z + " " + w;
	}

	public Vector4d clone() {
		Vector4d newVector3d = new Vector4d();
		newVector3d.x = x;
		newVector3d.y = y;
		newVector3d.z = z;
		newVector3d.w = w;

		return newVector3d;
	}

	public boolean equals(Object aThat) {
		if (this == aThat) {
			return true;
		} else if (!(aThat instanceof Vector4d)) {
			return false;
		} else {
			Vector4d that = (Vector4d) aThat;
			return EqualsUtil.areEqual(this.x, that.x) && EqualsUtil.areEqual(this.y, that.y) && EqualsUtil.areEqual(this.z, that.z) && EqualsUtil.areEqual(this.w, that.w);

		}
	}

	public int hashCode() {
		if (fHashCode == 0) {
			int result = HashCodeUtil.SEED;
			result = HashCodeUtil.hash(result, this.x);
			result = HashCodeUtil.hash(result, this.y);
			result = HashCodeUtil.hash(result, this.z);
			result = HashCodeUtil.hash(result, this.w);
			fHashCode = result;
		}
		return fHashCode;
	}
}
